Node.js - tutorial - file management

revision:


working with file descriptors in Node.js

Before you're able to interact with a file that sits in your filesystem, you must get a file descriptor. A file descriptor is a reference to an open file, a number (fd) returned by opening the file using the open() method offered by the fs module. This number (fd) uniquely identifies an open file in operating system.

example

JS:
        const fs = require('fs')
        fs.open('/Users/joe/test.txt', 'r', (err, fd) => {
            //fd is our file descriptor
        })
    

commonly used flags to interact with files are:

r - open the file for reading; r+ - open the file for reading and writing, if file don't exist it won't be created.
w+ - open the file for reading and writing, positioning the stream at the beginning of the file. The file is created if not existing.
a - open the file for writing, positioning the stream at the end of the file. The file is created if not existing.
a+ - open the file for reading and writing, positioning the stream at the end of the file. The file is created if not existing.

You can also open the file by using the fs.openSync method, which returns the file descriptor, instead of providing it in a callback.

Once you get the file descriptor, in whatever way you choose, you can perform all the operations that require it, like calling fs.close() and many other operations that interact with the filesystem.


Node.js file stats

Every file comes with a set of details that we can inspect using Node.js. In particular, using the stat() method provided by the fs module.

You call it passing a file path, and once Node.js gets the file details it will call the callback function you pass, with 2 parameters: an error message, and the file stats.

The file information is included in the stats variable. Following information can be extracted using the stats: if the file is a directory or a file, using stats.isFile() and stats.isDirectory(); if the file is a symbolic link using stats.isSymbolicLink(); the file size in bytes using stats.size.

example

JS:
      const fs = require('fs')
      fs.stat('/Users/joe/test.txt', (err, stats) => {
        if (err) {
          console.error(err)
          return
        }

        stats.isFile() //true
        stats.isDirectory() //false
        stats.isSymbolicLink() //false
        stats.size //1024000 //= 1MB
      })
    

Node.js file paths

Every file in the system has a path.You include this module in your files using "const path = require('path')" and you can start using its methods.

Given a path, you can extract information out of it using those methods: dirname: get the parent folder of a file; basename: get the filename part; extname: get the file extension.

You can get the file name without the extension by specifying a second argument to basename.

You can join two or more parts of a path by using path.join().
You can get the absolute path calculation of a relative path using path.resolve().
path.normalize() is another useful function, that will try and calculate the actual path, when it contains relative specifiers like . or .., or double slashes.


reading files with Node.js

The simplest way to read a file in Node.js is to use the fs.readFile() method, passing it the file path, encoding and a callback function that will be called with the file data (and the error).

example

JS:
      const fs = require('fs')

      fs.readFile('/Users/joe/test.txt', 'utf8' , (err, data) => {
        if (err) {
          console.error(err)
          return
        }
        console.log(data)
      })
    

Alternatively, you can use the synchronous version fs.readFileSync().

Both fs.readFile() and fs.readFileSync() read the full content of the file in memory before returning the data. This means that big files are going to have a major impact on your memory consumption and speed of execution of the program. In this case, a better option is to read the file content using streams.


writing files with Node.js

The easiest way to write to files in Node.js is to use the fs.writeFile() API.

example

JS:
      const fs = require('fs')

      const content = 'Some content!'
      
      fs.writeFile('/Users/joe/test.txt', content, err => {
        if (err) {
          console.error(err)
          return
        }
        //file written successfully
      })
     

Alternatively, you can use the synchronous version fs.writeFileSync().

By default, this API will replace the contents of the file if it does already exist.You can modify the default by specifying a flag:

r+ - open the file for reading and writing;
w+ - open the file for reading and writing, positioning the stream at the beginning of the file. The file is created if it does not exist;
a - open the file for writing, positioning the stream at the end of the file. The file is created if it does not exist.
a+ - open the file for reading and writing, positioning the stream at the end of the file. The file is created if it does not exist.

A handy method to append content to the end of a file is fs.appendFile() (and its fs.appendFileSync() counterpart).

All those methods write the full content to the file before returning the control back to your program (in the async version, this means executing the callback). In this case, a better option is to write the file content using streams.


working with folders in Node.js

The Node.js fs core module provides many handy methods you can use to work with folders.

Check if a folder exists: use fs.access() to check if the folder exists and Node.js can access it with its permissions.

Create a new folder: use fs.mkdir() or fs.mkdirSync() to create a new folder.

Read the content of a directory: use fs.readdir() or fs.readdirSync() to read the contents of a directory.

Rename a folder: use fs.rename() or fs.renameSync() to rename folder. The first parameter is the current path, the second the new path. fs.renameSync() is the synchronous version.

Remove a folder: use fs.rmdir() or fs.rmdirSync() to remove a folder. Removing a folder that has content can be more complicated than you need. You can pass the option { recursive: true } to recursively remove the contents.